#pragma once
#include <algorithm>
#include "./Light.hpp"
#include "./Mesh3D.hpp"
#include "./Camera.hpp"

class Scene {
public:
    Scene() {
        sceneBuffer.bind_to(opengl::buffer::uniform_buffer, 0);
        lightBuffer.bind_to(opengl::buffer::uniform_buffer, 2);
    }

public:
    std::vector<Entity3D *> entityArray{};
    size_t addEntity(Entity3D *entity) {
        entityArray.push_back(entity);
        return entityArray.size() - 1;
    }
    auto removeEntity(size_t index) {
        auto entity = entityArray.at(index);
        entityArray.erase(entityArray.begin() + index);
        return entity;
    }

public:
    PerspectiveCamera camera{};
    struct SceneBlock {
        glm::mat4 viewProjectionMatrix{1.0f};
        alignas(16) glm::vec3 cameraDir{1.0f, 0.0f, 0.0f};
        alignas(16) glm::vec3 cameraPos{0.0f};
    };
    opengl::buffer sceneBuffer{sizeof(SceneBlock)};

    void updateScene() {
        sceneBuffer.set_data(&camera.viewProjectionMatrix, sizeof(glm::mat4), 0);
        sceneBuffer.set_data(&camera.front, sizeof(glm::vec3), offsetof(SceneBlock, cameraDir));
        sceneBuffer.set_data(&camera.position, sizeof(glm::vec3), offsetof(SceneBlock, cameraPos));
    }

public:
    struct LightBlock {
        opengl::u32 numDirLights{0}, numPointLights{0};
        alignas(16) DirLight dirLight[4]{};
        PointLight pointLight[4]{};
    } lightning;
    opengl::buffer lightBuffer{sizeof(LightBlock)};

    void updateLightning() { lightBuffer.set_data(&lightning, sizeof(LightBlock)); }
    void addDirLight(const DirLight &light) {
        if (lightning.numDirLights < 4) {
            lightning.dirLight[lightning.numDirLights++] = light;
            updateLightning();
        }
    }
    void addPointLight(const PointLight &light) {
        if (lightning.numPointLights < 4) {
            lightning.pointLight[lightning.numPointLights++] = light;
            updateLightning();
        }
    }

public:
    void update() {
        camera.update_matrix();
        updateScene();
        updateLightning();
        for (auto entity : entityArray) {
            entity->update();
        }
    }
    void render() {
        for (auto entity : entityArray) {
            if (entity->visible) {
                entity->render();
            }
        }
    }
};